package com.ouroboroswiki.core.content.xslt;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import com.ouroboroswiki.core.AbstractContent;
import com.ouroboroswiki.core.Content;
import com.ouroboroswiki.core.ContentException;
import com.ouroboroswiki.core.ContentUtil;

public class XSLTContent extends AbstractContent {
	
	private static final Logger log = Logger.getLogger(XSLTContent.class.getName());
	
	public static final String PARAMETER_INPUT_MIME_TYPE = "input-mime-type";
	public static final String PARAMETER_INPUT_PATH = "input-path";
	public static final String PARAMETER_INPUT_REPO = "input-repo";
	public static final String PARAMETER_MAIN_PATH = "main-path";
	public static final String PARAMETER_MAIN_REPO = "main-repo";
	public static final String PARAMETER_INPUT_EDITABLE = "input-editable";
	
	
	private Content xmlContent;
	private Content xslContent;
	private Content propertiesContent;
	private TransformerFactory transformerFactory;
	private Map<String, Object> globalProperties;
	private Map<String, Content> loadedContent;
	protected URIResolver uriResolver;
	
	public XSLTContent( Content xmlContent, Content xslContent, Content propertiesContent) {
		this.xmlContent = xmlContent;
		this.xslContent = xslContent;
		this.propertiesContent = propertiesContent;
		this.loadedContent = new HashMap<String, Content>();
	}
	
	public void setUriResolver( URIResolver uriResolver ) {
		this.uriResolver = uriResolver;
	}
	
	public void setTransformerFactory( TransformerFactory transformerFactory ) {
		this.transformerFactory = transformerFactory;
	}
	
	
	@Override
	public Map<String, Content> getChildContent() {
		HashMap<String, Content> childContents = new HashMap<String, Content>(loadedContent);
		childContents.put(Content.CHILD_CONTENT_MAIN, xmlContent);
		childContents.put(XSLTContentRepository.XSL_CONTENT, xslContent);
		childContents.put(XSLTContentRepository.PARAMETER_CONTENT, propertiesContent);
		return childContents;
	}

	public Map<String, Object> getGlobalProperties() {
		return globalProperties;
	}
	
	public void addLoadedContent( String name, Content child ) {
		loadedContent.put( name, child );
	}

	public void setGlobalProperties(Map<String, Object> globalProperties) {
		this.globalProperties = globalProperties;
	}


	@Override
	public void write(OutputStream outs) throws IOException, ContentException {
		ByteArrayOutputStream xmlOutput = new ByteArrayOutputStream();
		xmlContent.write(xmlOutput);
		InputStream xmlInputStream = new ByteArrayInputStream(xmlOutput.toByteArray());
		
		ByteArrayOutputStream xslOutput = new ByteArrayOutputStream();
		xslContent.write( xslOutput );
		InputStream xslInputStream = new ByteArrayInputStream(xslOutput.toByteArray());
		
		String xmlURI = xmlContent.getUniqueName()+"."+xmlContent.getRepositoryName();
		String xslURI = xslContent.getUniqueName()+"."+xslContent.getRepositoryName();
		StreamSource xmlSource = new StreamSource( xmlInputStream, xmlURI );
		StreamSource xslSource = new StreamSource( xslInputStream, xslURI );
				
		Result result = getResult( outs );
		Transformer transformer;
		try {
			transformer = transformerFactory.newTransformer(xslSource);
		} catch( TransformerConfigurationException ex ) {
			throw new ContentException( "unable to create transformer "+xslContent, ex );
		}
		transformer.setURIResolver(this.uriResolver);
		transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes");
		String mimeType = xmlContent.getMimeType();
		if( mimeType != null ) {
			transformer.setParameter(PARAMETER_INPUT_MIME_TYPE, mimeType);
		}
		Content mainContent = null;
		Content nextMainContent = xmlContent;
		do{
			mainContent = nextMainContent;
			nextMainContent = mainContent.getChildContent(CHILD_CONTENT_MAIN);
		} while( nextMainContent != null );
		if( mainContent != null ) {
			String mainRepo = mainContent.getRepositoryName();
			if( mainRepo != null ) {
				transformer.setParameter(PARAMETER_MAIN_REPO, mainRepo);
			}
			String mainPath = mainContent.getUniqueName();
			if( mainPath != null ) {
				transformer.setParameter(PARAMETER_MAIN_PATH, mainPath);
			}
		}
		String repo = xmlContent.getRepositoryName();
		if( repo != null ) {
			transformer.setParameter(PARAMETER_INPUT_REPO, repo);
		}
		String path = xmlContent.getUniqueName();
		if( path != null ) {
			transformer.setParameter(PARAMETER_INPUT_PATH, path);
		}
		boolean writable = xmlContent.isWritable();
		transformer.setParameter(PARAMETER_INPUT_EDITABLE, Boolean.toString(writable));
		if( propertiesContent != null ) {
			Properties properties = new Properties();
			ByteArrayOutputStream propertiesOutput = new ByteArrayOutputStream();
			propertiesContent.write( propertiesOutput );
			InputStream propertiesInputStream = new ByteArrayInputStream( propertiesOutput.toByteArray() );
			properties.load(propertiesInputStream);
			for( Map.Entry<Object, Object> entry : properties.entrySet() ) {
				transformer.setParameter((String)entry.getKey(), entry.getValue());
			}
		}
		if( globalProperties != null ) {
			for( Map.Entry<String, Object> entry : globalProperties.entrySet() ) {
				transformer.setParameter(entry.getKey(), entry.getValue());
			}
		}
		
		try {
			transformer.transform(xmlSource, result);
			if( false && log.isLoggable(Level.INFO) ) {
				// transform again
				xmlSource = new StreamSource( new ByteArrayInputStream( xmlOutput.toByteArray() ), xmlURI );
				ByteArrayOutputStream bos = new ByteArrayOutputStream();
				StreamResult streamResult = new StreamResult(bos);
				transformer.transform(xmlSource, streamResult);
				log.log( Level.INFO, "generated : "+new String( bos.toByteArray() ) );
			}
		} catch( TransformerException ex ) {
			log.log( Level.INFO, "XML: "+ContentUtil.contentToString(xmlContent) );
			log.log( Level.INFO, "XSL: "+ContentUtil.contentToString(xmlContent) );
			throw new ContentException( "unable to transform XML "+xmlContent.getUniqueName()+" with XSL "+xslContent.getUniqueName(), ex, xmlContent.getUniqueName(), xslContent.getUniqueName() );
		}
	}

	protected Result getResult( OutputStream outs ) throws ContentException {
		return new StreamResult( outs );
	}
}
